package cc.verywell.pureblock.msg.nfs.linkmap;

import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentSkipListSet;

import cc.verywell.pureblock.log.Pblog;
import cc.verywell.pureblock.msg.nfs.strc.EncodeAble;
import cc.verywell.pureblock.util.AutoClosed;

/**
* 
* Copyright © 2019 TanYaqiu. All rights reserved.
* @author TanYaqiu 
* @address flat 601
* @date 2019年6月16日 下午3:36:05
*/
public class EasyFileControllerLog implements Closeable {
	final static public byte 
	TRNSATION = 1 ,
	MLOG_ADD = 2 ,MLOG_CUT = 3 ,
	HD_ADD = 4,HD_CUT = 5;

	private final Path file;
	private final OutputStream write;
	private ByteBuffer bb;
	private boolean closed = false;
	private long pos = 0;
	public EasyFileControllerLog(Path file) {
		this.file = file;
		try {
			write = Files.newOutputStream(file, StandardOpenOption.CREATE,StandardOpenOption.APPEND);
		} catch (IOException e) {
			throw new EasyIOException(e);
		}
		bb = ByteBuffer.allocate(17);
		bb.order(ByteOrder.BIG_ENDIAN);
	}
	@Override
	public void close(){
		if(closed)return;
		closed = true;
		try {
			write.close();
		} catch (IOException e) {
			if(Pblog.FS)Pblog.FS(e);
		}
	}
	public void delete() {
		close();
		try {
			Files.delete(file);
		} catch (IOException e) {
			if(Pblog.FS)Pblog.FS(e);
		}
	}
	
	private void addNumberLong(byte t,long l) throws IOException{
		bb.clear();
		bb.put(t);
		bb.putLong(l);
		write.write(bb.array(),0,9);
		pos+=9;
	}
	private void addNumberLong(byte t,long l1,long l2) throws IOException{
		bb.clear();
		bb.put(t);
		bb.putLong(l1);
		bb.putLong(l2);
		write.write(bb.array(),0,17);
		pos+=17;
	}
	
	private ByteBuffer tmp;
	private void addObjFile(byte t,EncodeAble l) throws IOException {
		int size = l.encodeSize();
		if(tmp==null || tmp.capacity()<size+5) {
			tmp = ByteBuffer.allocate(size+5);
			tmp.order(ByteOrder.BIG_ENDIAN);
		}
		else tmp.clear();
		tmp.put(t);
		tmp.putInt(size);
		l.encode(tmp);
		write.write(bb.array(),0,tmp.position());
		pos+=tmp.position();
	}
	
	
	
	
	
	public synchronized void writetedMemToHd(long memlog,List<EasyTableHDhead> add,long fseq,long seq) {
		try {
			addNumberLong(MLOG_CUT, memlog);
			if(add!=null) {
				for (EasyTableHDhead h : add) {
					addObjFile(HD_ADD, h);
				}
			}
			addNumberLong(TRNSATION,fseq,seq);
			write.flush();
		} catch (IOException e) {
			throw new EasyIOException(e);
		}
	}
	public synchronized void writetedNewMem(long memlog,long fseq,long seq) {
		try {
			addNumberLong(MLOG_ADD, memlog);
			addNumberLong(TRNSATION,fseq,seq);
			write.flush();
		} catch (IOException e) {
			throw new EasyIOException(e);
		}
	}
	public synchronized void writetedCompact(List<EasyTableHDhead> cut1,List<EasyTableHDhead> cut2,List<EasyTableHDhead> add,long fseq,long seq) {
		try {
			if(cut1!=null) {
				for (EasyTableHDhead h : cut1) {
					addNumberLong(HD_CUT, h.getNumber());
				}
			}
			if(cut2!=null) {
				for (EasyTableHDhead h : cut2) {
					addNumberLong(HD_CUT, h.getNumber());
				}
			}
			if(add!=null) {
				for (EasyTableHDhead h : add) {
					addObjFile(HD_ADD, h);
				}
			}
			addNumberLong(TRNSATION,fseq,seq);
			write.flush();
		} catch (IOException e) {
			throw new EasyIOException(e);
		}
	}
	public synchronized void writetedControllerLog(ControllerLog add,long fseq,long seq) {
		try {
			if(add.group!=null) {
				for (EasyTableHDhead h : add.group.getUnsort().files) {
					addObjFile(HD_ADD, h);
				}
				for (EasyTablesSorted s : add.group.getSorteds())
					for (EasyTableHDhead h : s.files) {
						addObjFile(HD_ADD, h);
					}
			}
			if(add.mlog!=null) {
				for (long l : add.mlog) {
					addNumberLong(HD_ADD, l);
				}
			}
			addNumberLong(TRNSATION,fseq,seq);
			write.flush();
		} catch (IOException e) {
			throw new EasyIOException(e);
		}
	}
	
	static public ControllerLog readLog(Path file,EasyFileController clr) {
		long fseq = 1;
		long seq = 1;
		HashSet<Long> mlogs = new HashSet<>();
		HashMap<Long,EasyTableHDhead> hds = new HashMap<>();
		
		ByteBuffer bb = ByteBuffer.allocate(16);
		bb.order(ByteOrder.BIG_ENDIAN);
		ByteBuffer tmp = null;
		long pos = 0;
		
		try(InputStream in = Files.newInputStream(file, StandardOpenOption.READ)){
			
			boolean trnsationEnd = true;
			ArrayList<TmpHead> tempHeads = new ArrayList<>();
			ArrayList<TmpLong> tempLongs = new ArrayList<>();
			
			int t;
			while((t=in.read())>=0) {
				pos++;
				switch(((byte)t)) {
				case TRNSATION:
					bb.clear();
					pos+=in.read(bb.array(), 0, 16);
					long tfseq = bb.getLong();
					long tseq = bb.getLong();
					
					if(fseq<tfseq)fseq=tfseq;
					if(seq<tseq)seq=tseq;
					for (TmpHead h : tempHeads) {
						if(h.isCut)hds.remove(h.l);
						else hds.put(h.l, h.head);
					}
					tempHeads.clear();
					for (TmpLong h : tempLongs) {
						if(h.isCut)mlogs.remove(h.l);
						else mlogs.add(h.l);
					}
					tempLongs.clear();
					trnsationEnd = true;
					continue;
					
				case MLOG_ADD:
					bb.clear();
					pos+=in.read(bb.array(), 0, 8);
					tempLongs.add(new TmpLong(bb.getLong(),false));
					trnsationEnd = false;
					continue;
					
				case MLOG_CUT:
					bb.clear();
					pos+=in.read(bb.array(), 0, 8);
					tempLongs.add(new TmpLong(bb.getLong(),true));
					trnsationEnd = false;
					continue;

				case HD_ADD:
					bb.clear();
					pos+=in.read(bb.array(), 0, 4);
					int size = bb.getInt();
					if(tmp==null || tmp.capacity()<size)tmp = ByteBuffer.allocate(size);
					else tmp.clear();
					pos+=in.read(tmp.array(),0,size);
					tmp.limit(size);
					EasyTableHDhead head = EasyTableHDhead.decode(bb,clr);
					
					tempLongs.add(new TmpHead(head));
					trnsationEnd = false;
					continue;
					
				case HD_CUT:
					bb.clear();
					pos+=in.read(bb.array(), 0, 8);
					tempHeads.add(new TmpHead(bb.getLong()));
					trnsationEnd = false;
					continue;

				default:
					throw new IOException("unknow mark: "+t);
				}
			}
			if(!trnsationEnd) {
				if(Pblog.FS)Pblog.FS(file," not end at trnsation");
			}
		} catch (IOException e) {
			if(Pblog.FS)Pblog.FS(e);
		}
		try {
			if(Files.size(file)!=pos) {
				if(Pblog.FS)Pblog.FS("Drop data ",Files.size(file)-pos," at ",file);
				try(AsynchronousFileChannel channel = AsynchronousFileChannel.open(file, StandardOpenOption.WRITE).truncate(pos)){}
			}
		} catch (IOException e) {
			throw new EasyIOException(e);
		}
		
		EasyTableGroup group;
		{
			List<List<EasyTableHDhead>> hdgs = new ArrayList<>(EasyFileController.MAX_GRADE+1);
			for(int i = -1;i<EasyFileController.MAX_GRADE;i++)hdgs.add(new ArrayList<>());
			
			for (EasyTableHDhead h : hds.values()) {
				if(h.grade()>EasyFileController.MAX_GRADE) throw new EasyIOException("HD "+h.getNumber()+" grade "+h.grade()+">MAX_GRADE:"+EasyFileController.MAX_GRADE);
				hdgs.get(h.grade()).add(h);
			}
			for (List<EasyTableHDhead> h : hdgs) {
				Collections.sort(h);
			}
			
			EasyTablesUnsort unsort = new EasyTablesUnsort(clr.getAC(), hdgs.get(0));
			EasyTablesSorted[] sorted = new EasyTablesSorted[EasyFileController.MAX_GRADE];
			for(int i = 0;i<EasyFileController.MAX_GRADE;i++)sorted[i] = new EasyTablesSorted(clr.getAC(),hdgs.get(i+1));
			group = new EasyTableGroup(unsort, sorted);
		}
		
		long[] mlog = null;
		if(!mlogs.isEmpty()) {
			Iterator<Long> it = mlogs.iterator();
			mlog = new long[mlogs.size()];
			for (int i = 0; i < mlog.length; i++) {
				mlog[i] = it.next();
			}
		}
		
		//TODO 多余文件清理
		
		
		return new ControllerLog(fseq, seq, group, mlog);
	}
	static private class TmpHead extends TmpLong{
		public EasyTableHDhead head;
		public TmpHead(long fseq) {
			super(fseq, true);
		}
		public TmpHead(EasyTableHDhead head) {
			super(head.getNumber(), false);
			this.head = head;
		}
	}
	static private class TmpLong{
		final public long l;
		final public boolean isCut;
		public TmpLong(long l, boolean isCut) {
			this.l = l;
			this.isCut = isCut;
		}
	}
	static public class ControllerLog{
		final public long fseq;
		final public long seq;
		final public EasyTableGroup group;
		final public long[] mlog;
		public ControllerLog(long fseq, long seq, EasyTableGroup group, long[] mlog) {
			this.fseq = fseq;
			this.seq = seq;
			this.group = group;
			this.mlog = mlog;
		}
		
	}
	


}
